import pygame, random, math, time

import sprite, player
import settings
import data
import gummworld2
import spritesheet
import os

class _BaseState(object):

    # @staticmethod
    # def enter(self):
        # self.image = self.images[0]

    @staticmethod
    def exit(self):
        pass
        
    @staticmethod
    def think(self, dt):
        self._update_facing(0)
        
##-----------------------------------------------------------------------------        

# Walk somewhere, then pause for a little while, then walk somewhere else
class Wander(_BaseState):

    @staticmethod
    def enter(self):
        self.vx, self.vy = 0., 0.
        self.speed = settings.zombie_wander_speed

    @staticmethod
    def think(self, dt):
        if random.random() < 1.2 * dt:  # 1.2 = avg seconds to walk in one direction
            if self.vx or self.vy: # stop moving
                self.vx, self.vy = 0.0, 0.0
            else:
                self._set_random_direction()

        # 0.5 = avg seconds before checking if you can see player
        if random.random() < 0.5 * dt and self.can_see_player(settings.zombie_wander_see_dist):
            return Follow
        self._update_animation(dt)

##-----------------------------------------------------------------------------        
        
class Follow(_BaseState):

    @staticmethod
    def enter(self):
        self.dangerous = True
        self.speed = settings.zombie_follow_speed

    @staticmethod
    def think(self, dt):
        if self.can_see_player(settings.zombie_wander_see_dist):
            self.approach(player.player.position)
        else:
            return Wander
        self._update_animation(dt)

    @staticmethod
    def exit(self):
        self.dangerous = False
        
##-----------------------------------------------------------------------------        

# After attacking the player, just chill out for 2 seconds
class Stand(_BaseState):

    @staticmethod
    def enter(self):
        self.vx, self.vy = 0., 0.
        self.statetimer = 2.
        # self.image = self.images[8]

    @staticmethod
    def think(self, dt):
        self._update_facing(8)
        self.statetimer -= dt
        if self.statetimer < 0.:
            return Follow if self.can_see_player(settings.zombie_wander_see_dist) else Wander

##-----------------------------------------------------------------------------        

# Reel back from the player after taking a hit
class BackOff(_BaseState):

    @staticmethod
    def enter(self):
        # self.image = self.images[0]
        self.statetimer = 0.35
        self.speed = -150.  # Because you're moving away
        self.approach(player.player.position)
        val = 80
        self.vx += random.randrange(-val, val)
        self.vy += random.randrange(-val, val)
        self.backwards = 180
        
    @staticmethod
    def exit(self):
        self.backwards = 0

    @staticmethod
    def think(self, dt):
        self._update_facing(0)
        self.statetimer -= dt
        if self.statetimer < 0.:
            return Stand if self.health > 0 else Dead

##-----------------------------------------------------------------------------        

class Dead(_BaseState):

    @staticmethod
    def enter(self):
        self.active = False

##-----------------------------------------------------------------------------        



class Zombie(sprite.Sprite):
    
    images = []
    frames = tuple()
    #image_cache = {} # {(frame, angle):image} might take much memory
    
    def __init__(self, position, *groups):
        if len(Zombie.images) == 0:
            # image = pygame.surface.Surface((20,20))
            # image.fill(pygame.Color('darkblue'))
            # self.images.append(image)
            # image = pygame.surface.Surface((20,20))
            # image.fill(pygame.Color('darkred'))
            # self.images.append(image)
            image_file = data.filepath(os.path.join('image','player0_b.png'))
            ss = spritesheet.SpriteSheet(image_file, True)
            Zombie.images = ss.load_strip((64,64), 9, margin=(64,0), padding=(0,0), colorkey=-1, orient='v')
            Zombie.frames = (0,1,2,3,4,5,6,7,8)
            self.image = self.images[8]
            for frame in range(len(Zombie.images)):
                for angle in range(360):
                    self.rotimage(frame, angle, 0.75)
        
        super(Zombie, self).__init__(
            source=self.images[0],
            position=position
        )
        
        self.x, self.y = self.rect.center
        self.vx, self.vy = 0., 0.
        
        self._current_state = Wander
        self._current_state.enter(self)
        self.health = settings.zombie_initial_health
        self.active = True  # When this is false, remove this sprite from existence
        self.dangerous = False  # Can the player be hurt by touching me?
        self.vulnerable = True  # Can I take damage from weapons?
        self.blit_rect = pygame.Rect(self.rect)
        # self.rect.width = 20
        # self.rect.height = 20
        self.time = 0
        self.backwards = 0

    def set_state(self, new_state):
        if __debug__: print self.__class__.__name__ + str(id(self)), "changing state", self._current_state.__name__, " -> ", new_state.__name__
        self._current_state.exit(self)
        self._current_state = new_state
        self._current_state.enter(self)

    def think(self, dt, walls):
        new_state = self._current_state.think(self, dt)
        if new_state:
            self.set_state(new_state)
        self.move(dt, walls)

    def hurt(self, amount):
        self.health -= amount
        print 'HIT: damage=%d, zombie health=%d'%(amount,self.health)
    
    def move(self, dt, walls):
        dx = self.vx * dt
        dy = self.vy * dt
        rect = self.rect.inflate(-1, -1)
        rect.center = self.x + dx, self.y + dy
        if rect.collidelist(walls) > -1:
            rect.center = self.x + dx, self.y
            if rect.collidelist(walls) == -1:
                self.x += dx
            rect.center = self.x, self.y + dy
            if rect.collidelist(walls) == -1:
                self.y += dy
        else:
            self.x += dx
            self.y += dy
        self.rect.center = self.x, self.y

    def hurt(self, damage):
        if self.vulnerable:
            self.health -= damage
            self.set_state(BackOff)
        
    def nom_nom(self):
        if self.dangerous:
            player.player.hurt(self, 10)  # TODO: amount of damage
            self.set_state(Stand)

    def _set_random_direction(self):
        a = random.random() * 6.283
        self.vx, self.vy = self.speed * math.sin(a), self.speed * math.cos(a)

    def can_see_player(self, radius):
        px, py = player.player.position
        return (self.x - px) ** 2 + (self.y - py) ** 2 < radius ** 2
        
    def _update_animation(self, dt):
        self.time += dt
        # number of images
        n_frames = len(self.frames) - 1
        # compute frame index from current time slice
        n = int(round(self.time % 1.0 * n_frames))
        frame = self.frames[n]
        self._update_facing(frame)

    @staticmethod
    def rotimage(frame, angle, factor = 1., cache = {}):
        key = (frame, int(angle / 9.) % 40, factor)
        if key not in cache:
            img = Zombie.images[frame]
            cache[key] = pygame.transform.rotozoom(img, angle, factor)
            if __debug__: print "cache miss key", key, "len cache:", len(cache)
        return cache[key]
            
    def _update_facing(self, frame):
        # compute image rotation and scale
        target = self.position[0] + self.vx, self.position[1] + self.vy
        self.facing = gummworld2.geometry.angle_of(self.position, target)
        angle = 360 - self.facing + self.backwards
        
        self.image = self.rotimage(frame, angle, 0.75)
        # compute the drawing rect
        self.blit_rect.size = self.image.get_size()
        self.blit_rect.center = self.rect.center

    def approach(self, (tx, ty)):
        dx, dy = tx - self.x, ty - self.y
        d = math.sqrt(dx ** 2 + dy ** 2)
        if not d: return
        self.vx = self.speed * dx / d
        self.vy = self.speed * dy / d

    def sign(self, n):
        if n:
            return 1.0 * n / abs(n)
        return n
##-----------------------------------------------------------------------------        
        
